package com.xiashitech.agent.instrumentation.struts2.inst;

import com.xiashitech.agent.cache.status.CacheStatus;
import com.xiashitech.agent.dto.UserRequest;
import com.xiashitech.agent.instrumentation.helper.calldepth.XSStruts2ProxyCallDepth;
import com.xiashitech.agent.instrumentation.utils.BaggageUtil;
import com.xiashitech.agent.utils.DurationPrinter;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Scope;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import static net.bytebuddy.matcher.ElementMatchers.*;
import static com.xiashitech.agent.instrumentation.struts2.XSStruts2ActionSingletons.instrumenter;
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;

/**
 * Deprecated
 * 本插桩是在接收到请求，创建 proxy 时做的插桩
 * 此时能拿到 action，能拿到 request 和 response
 * 但此时尚未执行 action，从 response 对象取不到 content-type
 */
public class XSStruts2ProxyCreateInstrumentation implements TypeInstrumentation {
    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        return named("org.apache.struts2.factory.StrutsActionProxyFactory");
    }

    @Override
    public void transform(TypeTransformer transformer) {
        transformer.applyAdviceToMethod(named("createActionProxy")
                .and(takesArgument(0,named("com.opensymphony.xwork2.ActionInvocation")))
                .and(takesArgument(1,named("java.lang.String")))
                .and(takesArgument(2,named("java.lang.String")))
                .and(takesArgument(3,named("java.lang.String")))
                .and(takesArgument(4,is(boolean.class)))
                .and(takesArgument(5,is(boolean.class))),
                XSStruts2ProxyCreateInstrumentation.class.getName() + "$XSProxyAdvice");
    }

    public static class XSProxyAdvice {
        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void onEnter(
                @Advice.This Object proxyFactory,
                @Advice.Local("otelCallDepth") CallDepth callDepth,
                @Advice.Local("otelContext") Context context,
                @Advice.Local("otelScope") Scope scope) {
            DurationPrinter durationPrinter = new DurationPrinter("XSStruts2ProxyCreateInstrumentation");
            if(CacheStatus.cannotRun()) {
                return;
            }
            callDepth = CallDepth.forClass(XSStruts2ProxyCallDepth.class);
            if (callDepth.getAndIncrement() > 0) {
                return;
            }
            Context parentContext = currentContext();
            if (!instrumenter().shouldStart(parentContext, proxyFactory)) {
                return;
            }
            parentContext = BaggageUtil.syncBaggage().build().storeInContext(parentContext);
            context = instrumenter().start(parentContext, proxyFactory);
            context = BaggageUtil.syncBaggage().build().storeInContext(context);
            scope = context.makeCurrent();
            durationPrinter.printElapseDuration("XSProxyAdvice b");
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void onExit(
                @Advice.Thrown Throwable throwable,
                @Advice.Return Object proxy,
                @Advice.Local("otelCallDepth") CallDepth callDepth,
                @Advice.Local("otelContext") Context context,
                @Advice.Local("otelScope") Scope scope) {
            DurationPrinter durationPrinter = new DurationPrinter("XSStruts2ProxyCreateInstrumentation");
            if(CacheStatus.cannotRun()) {
                return;
            }
            if (callDepth.decrementAndGet() > 0) {
                return;
            }
            if (scope != null) {
                scope.close();
                UserRequest ur = UserRequest.builder().build();
                ur.setAnyObject(proxy);
                ur.setPosition("createActionProxy");
                instrumenter().end(context, null , ur, throwable);
            }
            durationPrinter.printElapseDuration("XSProxyAdvice e");
        }
    }
}
